﻿/***************************************************************************
 * Copyright (c) reallyinfo Corporation 2019.
 * Developer: hainee
 * Email: hainee@163.com
 ***************************************************************************/
(function (root) {
  if (!HTMLCollection.prototype.forEach) {
    HTMLCollection.prototype.forEach = async function (cb, thisArg) {
      if (!cb || !cb.IsFunction) {
        return
      }
      let isAsync = cb.IsAsync
      let len = this.length
      for (var i = 0; i < len; i++) {
        let item = this[i]
        if (isAsync) {
          let res = await cb.call(thisArg, item, i)
          if (res === false) {
            return
          }
        } else {
          let res = cb.call(thisArg, item, i)
          if (res === false) {
            return
          }
        }
      }
    }
  }

  NodeList.prototype.forEach = HTMLCollection.prototype.forEach

  if (!HTMLCollection.prototype.map) {
    HTMLCollection.prototype.map = function (cb) {
      if (!cb) {
        return null
      }
      let res = []
      for (var i = 0; i < this.length; i++) {
        res.push(cb(this[i], i))
      }
      return res
    }
  }

  NodeList.prototype.map = HTMLCollection.prototype.map

  if (!HTMLCollection.prototype.findIndex) {
    HTMLCollection.prototype.findIndex = function (cb) {
      for (var i = 0; i < this.length; i++) {
        if (cb(this[i], i)) {
          return i
        }
      }
      return -1
    }
  }

  if (!Element.prototype.insertAt) {
    Element.prototype.insertAt = function (ele, idx) {
      this.insertBefore(ele, this.children[idx])
    }
  }

  // ********************* content types ***********************
  const contentTypes = {
    calculationChain: 'application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml',
    cellMetadata: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml',
    chart: 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml',
    chartColorStyle: 'application/vnd.ms-office.chartcolorstyle+xml',
    chartDrawing: 'application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml',
    chartsheet: 'application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml',
    chartStyle: 'application/vnd.ms-office.chartstyle+xml',
    commentAuthors: 'application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml',
    connections: 'application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml',
    coreFileProperties: 'application/vnd.openxmlformats-package.core-properties+xml',
    customFileProperties: 'application/vnd.openxmlformats-officedocument.custom-properties+xml',
    customization: 'application/vnd.ms-word.keyMapCustomizations+xml',
    customProperty: 'application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty',
    customXmlProperties: 'application/vnd.openxmlformats-officedocument.customXmlProperties+xml',
    diagramColors: 'application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml',
    diagramData: 'application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml',
    diagramLayoutDefinition: 'application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml',
    diagramPersistLayout: 'application/vnd.ms-office.drawingml.diagramDrawing+xml',
    diagramStyle: 'application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml',
    dialogsheet: 'application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml',
    digitalSignatureOrigin: 'application/vnd.openxmlformats-package.digital-signature-origin',
    documentSettings: 'application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml',
    drawings: 'application/vnd.openxmlformats-officedocument.drawing+xml',
    endnotes: 'application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml',
    excelAttachedToolbars: 'application/vnd.ms-excel.attachedToolbars',
    extendedFileProperties: 'application/vnd.openxmlformats-officedocument.extended-properties+xml',
    externalWorkbook: 'application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml',
    fontData: 'application/x-fontdata',
    fontTable: 'application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml',
    footer: 'application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml',
    footnotes: 'application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml',
    gif: 'image/gif',
    glossaryDocument: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml',
    handoutMaster: 'application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml',
    header: 'application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml',
    jpeg: 'image/jpeg',
    mainDocument: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml',
    notesMaster: 'application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml',
    notesSlide: 'application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml',
    numberingDefinitions: 'application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml',
    pict: 'image/pict',
    pivotTable: 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml',
    pivotTableCacheDefinition: 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml',
    pivotTableCacheRecords: 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml',
    png: 'image/png',
    presentation: 'application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml',
    presentationProperties: 'application/vnd.openxmlformats-officedocument.presentationml.presProps+xml',
    presentationTemplate: 'application/vnd.openxmlformats-officedocument.presentationml.template.main+xml',
    queryTable: 'application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml',
    relationships: 'application/vnd.openxmlformats-package.relationships+xml',
    ribbonAndBackstageCustomizations: 'http://schemas.microsoft.com/office/2009/07/customui',
    sharedStringTable: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml',
    singleCellTable: 'application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml',
    slicerCache: 'application/vnd.openxmlformats-officedocument.spreadsheetml.slicerCache+xml',
    slicers: 'application/vnd.openxmlformats-officedocument.spreadsheetml.slicer+xml',
    slide: 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml',
    slideComments: 'application/vnd.openxmlformats-officedocument.presentationml.comments+xml',
    slideLayout: 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml',
    slideMaster: 'application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml',
    slideShow: 'application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml',
    slideSyncData: 'application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml',
    styles: 'application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml',
    tableDefinition: 'application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml',
    tableStyles: 'application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml',
    theme: 'application/vnd.openxmlformats-officedocument.theme+xml',
    themeOverride: 'application/vnd.openxmlformats-officedocument.themeOverride+xml',
    tiff: 'image/tiff',
    trueTypeFont: 'application/x-font-ttf',
    userDefinedTags: 'application/vnd.openxmlformats-officedocument.presentationml.tags+xml',
    viewProperties: 'application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml',
    vmlDrawing: 'application/vnd.openxmlformats-officedocument.vmlDrawing',
    volatileDependencies: 'application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml',
    webSettings: 'application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml',
    wordAttachedToolbars: 'application/vnd.ms-word.attachedToolbars',
    wordprocessingComments: 'application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml',
    wordprocessingTemplate: 'application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml',
    workbook: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml',
    workbookRevisionHeader: 'application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml',
    workbookRevisionLog: 'application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml',
    workbookStyles: 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml',
    workbookTemplate: 'application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml',
    workbookUserData: 'application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml',
    worksheet: 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml',
    worksheetComments: 'application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml',
    worksheetSortMap: 'application/vnd.ms-excel.wsSortMap+xml',
    xmlSignature: 'application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml'
  }

  // *********** relationship types ***********
  const relationshipTypes = {
    alternativeFormatImport: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk',
    calculationChain: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain',
    cellMetadata: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata',
    chart: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart',
    chartColorStyle: 'http://schemas.microsoft.com/office/2011/relationships/chartColorStyle',
    chartDrawing: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes',
    chartsheet: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet',
    chartStyle: 'http://schemas.microsoft.com/office/2011/relationships/chartStyle',
    commentAuthors: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors',
    connections: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections',
    coreFileProperties: 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties',
    customFileProperties: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties',
    customization: 'http://schemas.microsoft.com/office/2006/relationships/keyMapCustomizations',
    customProperty: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/customProperty',
    customXml: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml',
    customXmlMappings: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/xmlMaps',
    customXmlProperties: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps',
    diagramColors: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramColors',
    diagramData: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData',
    diagramLayoutDefinition: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramLayout',
    diagramPersistLayout: 'http://schemas.microsoft.com/office/2007/relationships/diagramDrawing',
    diagramStyle: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramQuickStyle',
    dialogsheet: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet',
    digitalSignatureOrigin: 'http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin',
    documentSettings: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings',
    drawings: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
    endnotes: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes',
    excelAttachedToolbars: 'http://schemas.microsoft.com/office/2006/relationships/attachedToolbars',
    extendedFileProperties: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties',
    externalWorkbook: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink',
    font: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/font',
    fontTable: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable',
    footer: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer',
    footnotes: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes',
    glossaryDocument: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/glossaryDocument',
    handoutMaster: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/handoutMaster',
    header: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/header',
    image: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
    mainDocument: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument',
    notesSlide: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide',
    notesMaster: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster',
    numberingDefinitions: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering',
    pivotTable: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable',
    pivotTableCacheDefinition: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition',
    pivotTableCacheRecords: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords',
    presentation: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument',
    presentationProperties: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps',
    queryTable: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/queryTable',
    ribbonAndBackstageCustomizations: 'http://schemas.microsoft.com/office/2007/relationships/ui/extensibility',
    sharedStringTable: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings',
    singleCellTable: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableSingleCells',
    slide: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide',
    slideComments: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
    slideLayout: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout',
    slideMaster: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster',
    slideSyncData: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideUpdateInfo',
    styles: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles',
    tableDefinition: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table',
    tableStyles: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles',
    theme: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme',
    themeOverride: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride',
    thumbnail: 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail',
    userDefinedTags: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags',
    viewProperties: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps',
    vmlDrawing: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
    volatileDependencies: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies',
    webSettings: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings',
    wordAttachedToolbars: 'http://schemas.microsoft.com/office/2006/relationships/attachedToolbars',
    wordprocessingComments: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
    workbook: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument',
    workbookRevisionHeader: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/revisionHeaders',
    workbookRevisionLog: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/revisionLog',
    workbookStyles: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles',
    workbookUserData: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/usernames',
    worksheet: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet',
    worksheetComments: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
    worksheetSortMap: 'http://schemas.microsoft.com/office/2006/relationships/wsSortMap',
    xmlSignature: 'http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature'
  }

  const XML_FILE_PREFIX = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'

  const PACKAGE_RELATIONSHAIPS_XML = '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'

  const PRESENTATION_PATH = 'ppt/presentation.xml'

  const PRESENTATION_RELS_PATH = 'ppt/_rels/presentation.xml.rels'

  const NULL_IMAGE = 'ppt/media/null.png'

  const NAMESPACE = {
    p: 'http://schemas.openxmlformats.org/presentationml/2006/main'
  }
  const UTF8_ARRAY = 'uint8array'

  function getRelativePath(rootPath, relativePath) {
    var paths = rootPath.split('/')
    if (rootPath.endsWith('/')) {
      paths.pop()
    }
    var relativePaths = relativePath.split('/')
    while (relativePaths[0] === '..') {
      paths.pop()
      relativePaths.shift()
    }
    return `${paths.join('/')}/${relativePaths.join('/')}`
  }

  function getSlidePath(slideName) {
    return `ppt/slides/${slideName}.xml`
  }

  function throwErrorInvalidDocumentFileNotFound(file) {
    throw new Error(`文档格式无效，未找到指定文件[${file}]！`)
  }

  /**
   * 定义openxml模板渲染对象，可使用art-template语法渲染xml所有文件
   * js库依赖：JSZip(https://github.com/Stuk/jszip)、art-template(https://github.com/aui/art-template)
   * @param {any} JSZip
   * @param {any} template
   * @returns JSZip
   */
  function defineOpenXmlXP(JSZip, template) {
    const CONTENT_TYPE_FILE = '[Content_Types].xml'

    async function powerPointRenderHandler(docJSZip, xmlJSFile, xmlContent, dataObject) {
      let fileDoc = (new DOMParser()).parseFromString(xmlContent, 'text/xml')
      let fileInfo = openxmlXPInstance.getFileInfo(xmlJSFile.name)
      let relsObj = await openxmlXPInstance.getFileRels(docJSZip, fileInfo)
      let updateCounter = 0

      // 处理每个文档中的图片
      let picEles = fileDoc.querySelectorAll('pic')
      if (picEles && picEles.length) {
        await picEles.forEach(async (picEle, idx) => {
          let res = await openxmlXPInstance.updateImageElement(docJSZip, picEle, dataObject, fileInfo, relsObj)
          res && updateCounter++
        })
      }

      // 处理每个文档中的文本节点
      let textEles = fileDoc.querySelectorAll('sp')
      if (textEles && textEles.length) {
        await textEles.forEach(async (txtEle, idx) => {
          let res = await openxmlXPInstance.updateTextElement(docJSZip, txtEle, dataObject, fileInfo, relsObj)
          res && updateCounter++
        })
      }

      // 处理每个文档中的Table节点
      let tblEles = fileDoc.querySelectorAll('graphicFrame > graphic > graphicData > tbl')
      if (tblEles && tblEles.length) {
        await tblEles.forEach(async (tblEle, idx) => {
          let res = await openxmlXPInstance.updateTableElement(docJSZip, tblEle, dataObject, fileInfo, relsObj)
          res && updateCounter++
        })
      }

      // 处理每个文档中的chart节点
      let chartEles = fileDoc.querySelectorAll('graphicFrame > graphic > graphicData > chart')
      if (chartEles && chartEles.length) {
        await chartEles.forEach(async (chartEle, idx) => {
          await openxmlXPInstance.updateChartElement(docJSZip, chartEle, dataObject, fileInfo, relsObj)
        })
      }

      if (updateCounter > 0) {
        docJSZip.file(relsObj.fullPath, relsObj.doc.documentElement.outerHTML)
        return {
          changed: true,
          content: fileDoc.documentElement.outerHTML
        }
      }
      return {
        changed: false
      }
    }

    async function wordRenderHandler(docJSZip, xmlJSFile, xmlContent, dataObject, onProgress) {
      //整个渲染过程的进度按100%算
      let fileDoc = (new DOMParser()).parseFromString(xmlContent, 'text/xml')
      let fileInfo = openxmlXPInstance.getFileInfo(xmlJSFile.name)
      let relsObj = await openxmlXPInstance.getFileRels(docJSZip, fileInfo)
      let allEles = fileDoc.querySelectorAll('pic,sp,graphic > graphicData > tbl,graphic > graphicData > chart, AlternateContent')
      let updateCounter = 0

      if (allEles && allEles.length) {
        await allEles.forEach(async (ele, idx) => {
          let res = 0
          switch (ele.localName.toLowerCase()) {
            case 'pic':
              // 处理文档中的图片
              res = await openxmlXPInstance.updateImageElement(docJSZip, ele, dataObject, fileInfo, relsObj, '', false)
              break;

            case 'alternatecontent':
              // 处理文档中的文本框节点
              debugger
              res = await openxmlXPInstance.updateWordTextElement(docJSZip, ele, dataObject, fileInfo, relsObj)
              break;

            case 'tbl':
              // 处理文档中的Table节点
              res = await openxmlXPInstance.updateTableElement(docJSZip, ele, dataObject, fileInfo, relsObj, '', false)
              break;

            case 'chart':
              // 处理文档中的chart节点
              res = await openxmlXPInstance.updateChartElement(docJSZip, ele, dataObject, fileInfo, relsObj, false)
              break;
          }
          res && updateCounter++
          onProgress && onProgress(updateCounter / allEles.length)
        })
      }
      if (updateCounter > 0) {
        docJSZip.file(relsObj.fullPath, relsObj.doc.documentElement.outerHTML)
        onProgress && onProgress(100)
        return {
          changed: true,
          content: fileDoc.documentElement.outerHTML
        }
      }
      onProgress && onProgress(100)
      return {
        changed: false
      }
    }

    /**
     * 修复文档中的模板标记错误
     * */
    async function repairXmlTemplateTags(xml) {
      let xmlDoc = (new DOMParser()).parseFromString(xml, 'text/xml')
      let allTEles = xmlDoc.documentElement.querySelectorAll('r>t')
      let startTagEle = null
      await allTEles.forEach(async (tEle) => {
        let txt = tEle.textContent
        let idxStart = txt.indexOf('{{')
        let idxEnd = txt.indexOf('}}')
        if (idxStart >= 0 && !startTagEle) {
          if (idxEnd > idxStart) {
            return
          }
          startTagEle = tEle
        } else {
          if (startTagEle) {
            startTagEle.insertAdjacentText('beforeend', txt)
            tEle.parentNode.remove()
            if (idxEnd >= 0 && idxStart < 0) {
              startTagEle = null
            }
          }
        }

      })
      return xmlDoc.documentElement.outerHTML
    }

    /**
     * 渲染指定的文件
     * @param {any} docJSZip
     * @param {any} fileName
     * @param {any} dataObject
     * @param {any} onRenderFile
     */
    async function renderFile(docJSZip, fileName, dataObject, onRenderFile, onProgress) {
      const ext = fileName.substring(fileName.lastIndexOf('.')).toLowerCase()
      if (ext === '.xml' || ext === '.rels') {
        // 如果是xml文档，则直接进行渲染
        try {
          var xmlFile = docJSZip.files[fileName]
          if (!xmlFile) {
            console.error(`指定的文件不存在！ ${fileName}`)
            return null
          }
          var content = await xmlFile.async('text')
          onProgress && onProgress(5)
          var xml = content
          if (content.indexOf('{{') >= 0) {
            content = await repairXmlTemplateTags(content)
            onProgress && onProgress(10)
            xml = template.render(content, dataObject)
            onProgress && onProgress(15)
          }
          onRenderFile = onRenderFile || powerPointRenderHandler
          if (onRenderFile) {
            let res = await onRenderFile(docJSZip, xmlFile, xml, dataObject, (val) => {
              onProgress && onProgress(30 + val * 0.6)
            })
            if (res.changed) {
              xml = res.content
            }
          }
          if (xml !== content) {
            docJSZip.file(fileName, xml)
          }
          onProgress && onProgress(100)
          return xmlFile
        } catch (ex) {
          console.error(`render file error:${fileName}`)
          debugger
        }
      } else if (ext === '.xlsx') {
        // 如果是嵌入的数据资源，则递归渲染
        var xlsxFileData = await docJSZip.files[fileName].async(UTF8_ARRAY)
        var xlsxJSZip = new JSZip()
        await xlsxJSZip.loadAsync(xlsxFileData)
        await openxmlXPInstance.renderZip(xlsxJSZip, dataObject)
        try {
          xlsxFileData = await xlsxJSZip.generateAsync({ type: UTF8_ARRAY })
          docJSZip.file(fileName, xlsxFileData, { binary: true })
          return xlsxJSZip
        } catch (ex) {
          console.error(ex.message)
          debugger
        }
      }
      return null
    }

    function getFileInfo(filePath) {
      const paths = filePath.split('/')
      const name = paths.pop()
      const idx = name.indexOf('.')
      const orgName = idx >= 0 ? name.substring(0, idx) : name
      const ext = idx >= 0 ? name.substring(idx) : ''
      return {
        fullPath: filePath,
        path: paths.join('/'),
        name,
        orgName,
        ext
      }
    }

    function parseRelsPath(relsPath, rootPath) {
      return relsPath.startsWith('..') ? getRelativePath(rootPath, relsPath) : (relsPath.startsWith('/') ? relsPath.substring(1) : relsPath)
    }

    function joinPaths() {
      let paths = []
      for (var i = 0; i < arguments.length; i++) {
        let path = arguments[i]
        if (path) {
          if (path.IsFunction) {
            path = path()
          }
          if (path.startsWith('/')) {
            path = path.substring(1)
          }
        }
        path && paths.push(path)
      }
      return paths.join('/')
    }

    let openxmlXPInstance = {
      NullImage: {
        name: NULL_IMAGE,
        data: 'iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAEYklEQVR4Xu3UAQkAAAwCwdm/9HI83BLIOdw5AgQIRAQWySkmAQIEzmB5AgIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlAABg+UHCBDICBisTFWCEiBgsPwAAQIZAYOVqUpQAgQMlh8gQCAjYLAyVQlKgIDB8gMECGQEDFamKkEJEDBYfoAAgYyAwcpUJSgBAgbLDxAgkBEwWJmqBCVAwGD5AQIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlAABg+UHCBDICBisTFWCEiBgsPwAAQIZAYOVqUpQAgQMlh8gQCAjYLAyVQlKgIDB8gMECGQEDFamKkEJEDBYfoAAgYyAwcpUJSgBAgbLDxAgkBEwWJmqBCVAwGD5AQIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlAABg+UHCBDICBisTFWCEiBgsPwAAQIZAYOVqUpQAgQMlh8gQCAjYLAyVQlKgIDB8gMECGQEDFamKkEJEDBYfoAAgYyAwcpUJSgBAgbLDxAgkBEwWJmqBCVAwGD5AQIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlAABg+UHCBDICBisTFWCEiBgsPwAAQIZAYOVqUpQAgQMlh8gQCAjYLAyVQlKgIDB8gMECGQEDFamKkEJEDBYfoAAgYyAwcpUJSgBAgbLDxAgkBEwWJmqBCVAwGD5AQIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlAABg+UHCBDICBisTFWCEiBgsPwAAQIZAYOVqUpQAgQMlh8gQCAjYLAyVQlKgIDB8gMECGQEDFamKkEJEDBYfoAAgYyAwcpUJSgBAgbLDxAgkBEwWJmqBCVAwGD5AQIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlAABg+UHCBDICBisTFWCEiBgsPwAAQIZAYOVqUpQAgQMlh8gQCAjYLAyVQlKgIDB8gMECGQEDFamKkEJEDBYfoAAgYyAwcpUJSgBAgbLDxAgkBEwWJmqBCVAwGD5AQIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlAABg+UHCBDICBisTFWCEiBgsPwAAQIZAYOVqUpQAgQMlh8gQCAjYLAyVQlKgIDB8gMECGQEDFamKkEJEDBYfoAAgYyAwcpUJSgBAgbLDxAgkBEwWJmqBCVAwGD5AQIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlAABg+UHCBDICBisTFWCEiBgsPwAAQIZAYOVqUpQAgQMlh8gQCAjYLAyVQlKgIDB8gMECGQEDFamKkEJEDBYfoAAgYyAwcpUJSgBAgbLDxAgkBEwWJmqBCVAwGD5AQIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlAABg+UHCBDICBisTFWCEiBgsPwAAQIZAYOVqUpQAgQMlh8gQCAjYLAyVQlKgIDB8gMECGQEDFamKkEJEDBYfoAAgYyAwcpUJSgBAgbLDxAgkBEwWJmqBCVAwGD5AQIEMgIGK1OVoAQIGCw/QIBARsBgZaoSlACBB1YxAJfjJb2jAAAAAElFTkSuQmCC'
      },
      repairXmlTemplateTags,
      contentTypes,
      relationshipTypes,
      getFileInfo,
      getRelativePath,
      async renderZip(docJSZip, dataObject, onProgressCallback) {
        var contentTypeFile = docJSZip.files[CONTENT_TYPE_FILE]
        if (contentTypeFile === null) {
          throwErrorInvalidDocumentFileNotFound(CONTENT_TYPE_FILE)
        }
        var files = Object.keys(docJSZip.files)
        for (var i = 0; i < files.length; i++) {
          await renderFile(docJSZip, files[i], dataObject)
          onProgressCallback &&
            onProgressCallback(parseInt(((i + 1) * 100) / files.length))
        }
        return docJSZip
      },

      async renderBlob(fileData, dataObject, onProgressCallback) {
        var zip = await this.loadBlob(fileData)
        return this.renderZip(zip, dataObject, onProgressCallback)
      },

      async renderBase64(fileData, dataObject, onProgressCallback) {
        var zip = await this.loadBase64(fileData)
        return this.renderZip(zip, dataObject, onProgressCallback)
      },

      fileRenders: {
        powerPoint: powerPointRenderHandler,
        word: wordRenderHandler
      },

      renderFile,

      /**
       * 渲染指定名称的一个slide
       * @param {any} docJSZip
       * @param {any} slideName
       * @param {any} dataObject
       */
      async renderSlide(docJSZip, slideName, dataObject, onRender) {
        let slidePath = getSlidePath(slideName)
        let slideZip = await renderFile(docJSZip, slidePath, dataObject, onRender)
        return slideZip
      },

      /**
       * 渲染指定多个名称集合的slide
       * @param {any} docJSZip
       * @param {any} slideNames
       * @param {any} dataObject
       */
      async renderSlides(docJSZip, slideNames, dataObject, progressCB, onRender) {
        if (slideNames === null || slideNames.length === 0) {
          let { slideFiles } = await this.getPresentationConfig(docJSZip)
          slideNames = slideFiles.map(slide => slide.orgName)
        }
        if (typeof (slideNames) === 'string') {
          slideNames = slideNames.split(',')
        }
        for (var i = 0; i < slideNames.length; i++) {
          await this.renderSlide(docJSZip, slideNames[i], dataObject, onRender)
          progressCB && progressCB(parseInt((i + 1) * 100 / slideNames.length))
        }
      },

      async loadBase64(fileData) {
        var zip = new JSZip()
        await zip.loadAsync(fileData, { base64: true })
        return zip
      },

      async loadBlob(fileData) {
        var zip = new JSZip()
        await zip.loadAsync(fileData, { binary: true })
        return zip
      },

      async getFiles(zipFile, createItemCB) {
        let root = {
          name: '/',
          path: '/',
          zipFile: zipFile,
          items: [],
          dir: true
        }
        var itemCounter = 0
        createItemCB && (await createItemCB(root, itemCounter))
        let allFolders = {
          '/': root
        }
        let keys = Object.keys(zipFile.files)
        for (var i = 0; i < keys.length; i++) {
          let key = keys[i]
          itemCounter++
          let item = zipFile.files[key]
          let paths = key.split('/')
          paths = paths.filter(k => k.length > 0)
          let name = paths.pop()
          let path = `${paths.join('/')}/`
          let folder = root
          if (path !== '/') {
            if (!allFolders[path]) {
              let tmpPaths = []
              let tmpFolders = []
              path
                .split('/')
                .filter(p => p.length > 0)
                .forEach(p => {
                  tmpPaths.push(p)
                  let tmpPath = `${tmpPaths.join('/')}/`
                  let tmpFolder = allFolders[tmpPath]
                  if (!tmpFolder) {
                    allFolders[tmpPath] = tmpFolder = {
                      name: p,
                      path: tmpPath,
                      items: [],
                      root,
                      dir: true
                    }
                    folder.items.push(tmpFolder)
                    tmpFolders.push(tmpFolder)
                  }
                  folder = tmpFolder
                })
              if (createItemCB) {
                for (var t = 0; t < tmpFolders.length; t++) {
                  itemCounter++
                  await createItemCB(tmpFolders[t], itemCounter)
                }
              }
            } else {
              folder = allFolders[path]
            }
          }
          if (!item.dir) {
            let file = getFileInfo(key)
            file.dir = false
            file.root = root
            file.folder = folder
            createItemCB && (await createItemCB(file, itemCounter))
            folder.items.push(file)
          }
        }
        return [root]
      },

      /**
       * 获取PPTX文档的presentation.xml的配置
       * @param {any} zipFile
       */
      async getPresentationConfig(zipFile) {
        let presentationDoc = await this.getXmlDocument(
          zipFile.file(PRESENTATION_PATH)
        )
        let presentationDocRels = await this.getXmlDocument(
          zipFile.file(PRESENTATION_RELS_PATH)
        )
        let slideIdList = presentationDoc.getElementsByTagNameNS(NAMESPACE.p, 'sldId')
        let slideFiles = slideIdList.map(item => {
          let slideId = item.getAttribute('r:id')
          let slideRel = presentationDocRels.querySelector(`Relationship[Id=${slideId}]`)
          let slideFilePath = slideRel.getAttribute('Target')
          slideFilePath = parseRelsPath(slideFilePath, 'ppt')
          return getFileInfo(slideFilePath)
        })

        return {
          slideFiles,
          presentationDoc,
          presentationDocRels
        }
      },

      /**
       * 获取所有的slide文件的名称
       * @param {any} zipFile
       */
      async getSlideNames(zipFile) {
        let { slideFiles } = await this.getPresentationConfig(zipFile)
        return slideFiles.map(slide => slide.orgName)
      },

      /**
       * 将指定xml的zip文件解析成xml文档对象
       * @param {any} zipFile
       * @param {any} type
       */
      async getXmlDocument(zipFile, type) {
        try {
          type = type || 'text'
          let content = await zipFile.async(type)
          let parser = new DOMParser()
          return parser.parseFromString(content, 'text/xml')
        } catch (ex) {
          debugger
        }
      },

      /**
       * 
       * @param {any} zipFile
       */
      async getContentTypesData(zipFile) {
        let file = await zipFile.files[CONTENT_TYPE_FILE]
        let doc = await this.getXmlDocument(file)
        let root = doc.querySelector('Types')
        let types = {}
        doc.querySelectorAll('Override').forEach(ele => {
          let name = ele.getAttribute('PartName')
          let contentType = ele.getAttribute('ContentType')
          types[name] = contentType
        })
        return {
          file,
          doc,
          types,
          root
        }
      },

      /**
       * 
       * @param {any} zipFile
       */
      async getContentTypesDefaultData(zipFile) {
        let file = await zipFile.files[CONTENT_TYPE_FILE]
        let doc = await this.getXmlDocument(file)
        let root = doc.querySelector('Types')
        let types = {}
        doc.querySelectorAll('Default').forEach(ele => {
          let name = ele.getAttribute('Extension')
          let contentType = ele.getAttribute('ContentType')
          types[name] = contentType
        })
        return {
          file,
          doc,
          types,
          root
        }
      },

      /**
       * 
       * @param {any} contentTypesData
       * @param {any} partName
       * @param {any} contentType
       */
      addContentType(contentTypesData, partName, contentType) {
        if (!contentType) {
          return
        }
        let root = contentTypesData.root
        let typeEle = contentTypesData.doc.createElement('Override')
        typeEle.setAttribute('PartName', partName)
        typeEle.setAttribute('ContentType', contentType)
        root.insertAdjacentHTML('beforeend', typeEle.outerHTML)
      },

      /**
       * 加载指定路径文档的所有关联文件
       * @param {any} zip 文档ZIP对象
       * @param {any} files 文件的路径，可以是","号分隔的字符串 或 Array
       */
      async loadDocFiles(zip, files) {
        var fileCaches = {}
        var fileObjs = []

        async function _loadFile(docJSZip, filePath, fileCaches) {
          console.log(`load file ${filePath}`)
          if (fileCaches[filePath]) {
            return fileCaches[filePath]
          }
          var rels = []
          var fileInfo = getFileInfo(filePath)
          var docFile = docJSZip.file(filePath)
          if (!docFile) {
            throw new Error(`未找到文档文件：'${filePath}'！`)
          }
          var relsFile = docJSZip.file(
            `${fileInfo.path}/_rels/${fileInfo.name}.rels`
          )
          var fileResult = (fileCaches[filePath] = {
            file: docFile,
            fileInfo,
            rels,
            type: contentTypes.slide
          })
          if (relsFile != null) {
            const relsDoc = await this.getXmlDocument(relsFile)
            // 获取文档的rels
            var relationships = relsDoc.querySelectorAll('Relationship') || []
            for (var i = 0; i < relationships.length; i++) {
              let item = relationships[i]
              let targetFile = item.getAttribute('Target')
              let id = item.getAttribute('Id')
              let type = item.getAttribute('Type') || relationshipTypes.slide
              let targetFilePath = parseRelsPath(targetFile, fileInfo.path)
              let target = null
              switch (type) {
                case relationshipTypes.vmlDrawing:
                case relationshipTypes.chart:
                  target = await _loadFile.call(
                    this,
                    docJSZip,
                    targetFilePath,
                    fileCaches
                  )
                  break

                default:
                  if (fileCaches[targetFilePath]) {
                    target = fileCaches[targetFilePath]
                  } else {
                    fileCaches[targetFilePath] = target = {
                      type,
                      file: docJSZip.file(targetFilePath),
                      fileInfo: getFileInfo(targetFilePath)
                    }
                  }
                  break
              }
              rels.push({
                id,
                type,
                target,
                targetFile
              })
            }
          }
          return fileResult
        }

        files = files instanceof Array ? files : files.split(',')
        for (var i = 0; i < files.length; i++) {
          const filePath = files[i]
          const fileObj = await _loadFile.call(this, zip, filePath, fileCaches)
          fileObjs.push(fileObj)
        }
        return fileObjs
      },

      /**
       * 根据Tag数据中的name属性值查找slide并返回slide的name
       */
      async getSlideByTagName(docJSZip, searchName, slideNames) {
        let resultSlideName = null
        slideNames = slideNames || await this.getSlideNames(docJSZip)
        await slideNames.forEachAsync(async slideName => {
          let slidePath = `ppt/slides/${slideName}.xml`
          let slideFile = docJSZip.files[slidePath]
          if (!slideFile) {
            return
          }
          let slideDoc = await this.getXmlDocument(slideFile)
          if (!slideDoc) {
            return
          }
          let slideEle = slideDoc.documentElement.querySelector('cSld')
          if (!slideEle) {
            return
          }
          let fileInfo = getFileInfo(slidePath)
          let tagsData = await this.getElementTagConfig(docJSZip, slideEle, fileInfo, null, 'cSld > custDataLst tags')
          if (!tagsData) {
            return
          }
          let tagsConfigText = tagsData.data
          if (!tagsConfigText) {
            return
          }
          let tagsConfig = JSON.parse(tagsConfigText)
          if (!tagsConfig.name) {
            return
          }
          if (tagsConfig.name === searchName) {
            resultSlideName = slideName
            return false
          }
        })
        return resultSlideName
      },

      /**
       * 获取所有Slide对应的TagsData.Name
       */
      async getSlideTagNames(docJSZip, slideNames) {
        let result = {}
        slideNames = slideNames || await this.getSlideNames(docJSZip)
        await slideNames.forEachAsync(async (slideName, pos) => {
          let slidePath = `ppt/slides/${slideName}.xml`
          let slideFile = docJSZip.files[slidePath]
          if (!slideFile) {
            return
          }
          let slideDoc = await this.getXmlDocument(slideFile)
          if (!slideDoc) {
            return
          }
          let slideEle = slideDoc.documentElement.querySelector('cSld')
          if (!slideEle) {
            return
          }
          let fileInfo = getFileInfo(slidePath)
          let tagsData = await this.getElementTagConfig(docJSZip, slideEle, fileInfo, null, 'cSld > custDataLst tags')
          if (!tagsData) {
            return
          }
          let tagsConfigText = tagsData.data
          if (!tagsConfigText) {
            return
          }
          let tagsConfig = JSON.parse(tagsConfigText)
          if (!tagsConfig.name) {
            return
          }
          result[tagsConfig.name] = {
            pos,
            slideName
          }
        })
        return result
      },

      /**
       * 获取所有slideLayout文件
       * @param {any} docJSZip
       */
      async getSlideLayouts(docJSZip) {
        let result = {}
        let slideLayoutFolder = docJSZip.folder(`ppt/slideLayouts/`)
        if (!slideLayoutFolder) {
          return result
        }
        let slideLayoutFiles = []
        await slideLayoutFolder.forEach((path, file) => {
          if (file.dir || path.lastIndexOf('.rels') > 0) {
            return
          }
          slideLayoutFiles.push(file)
        })
        await slideLayoutFiles.forEachAsync(async zipFile => {
          let doc = await this.getXmlDocument(zipFile)
          let cSldEle = doc.querySelector('cSld')
          let name = cSldEle.getAttribute('name')
          result[name] = {
            doc,
            zipFile,
            fileInfo: getFileInfo(zipFile.name)
          }
        })
        return result
      },

      /**
       * 指定PPTX文档的名称生成器
       * @param {any} docJSZip
       */
      async slideNameGenerator(docJSZip) {
        return {
          getSlideIndex() { },
          getTagIndex() { }
        }
      },

      /**
       * 
       * @param {any} orgName
       * @param {any} fileNO
       */
      generatorFileName(orgName, fileNO) {
        return orgName.replace(/\d$/, fileNO)
      },

      /**
       * 将若干个slide复制并添加到当前幻灯片的指定索引位置
       * @param {any} targetDocJSZip  当前文档ZIP文件对象
       * @param {any} srcDocJSZip  源文档ZIP文件对象
       * @param {Object} srcSlideNames  源Slide名称：slide1,slide2 或 ['slide1', 'slide2']
       * @param {Function} progressCB 进度回调
       * @param {any} insertPos  要被插入的位置，默认最后面
       */
      async copySlide(
        targetDocJSZip,
        srcDocJSZip,
        srcSlideNames,
        progressCB,
        insertPos,
        deepCopy,
        defaultSlideLayout,
        defaultSlideMaster
      ) {
        defaultSlideLayout = defaultSlideLayout || 'slideLayout1'
        defaultSlideMaster = defaultSlideMaster || 'slideMaster1'
        var contentTypesFile = targetDocJSZip.file(CONTENT_TYPE_FILE)
        if (!contentTypesFile) {
          throw new Error(`未找到文件：'${CONTENT_TYPE_FILE}'`)
        }
        var contentTypesDoc = await this.getXmlDocument(contentTypesFile)
        var contentTypesListEle = contentTypesDoc.documentElement

        var targetPresentationDoc = await this.getXmlDocument(
          targetDocJSZip.file(PRESENTATION_PATH)
        )
        var targetSlideIdList = targetPresentationDoc.getElementsByTagNameNS(
          NAMESPACE.p,
          'sldIdLst'
        )[0]
        if (!targetSlideIdList) {
          throw new Error('未找到节点[p:sldIdLst]！')
        }
        var targetPresentationDocRels = await this.getXmlDocument(
          targetDocJSZip.file(PRESENTATION_RELS_PATH)
        )
        var targetSlideRels = targetPresentationDocRels.querySelector(
          'Relationships'
        )
        var maxSlideId = 1
        var maxSlideNO = targetSlideIdList.children.length + 1

        var srcPresentationDoc = await this.getXmlDocument(
          srcDocJSZip.file(PRESENTATION_PATH)
        )
        var srcPresentationDocRels = await this.getXmlDocument(
          srcDocJSZip.file(PRESENTATION_RELS_PATH)
        )
        var srcSlideIdList = srcPresentationDoc.getElementsByTagNameNS(
          NAMESPACE.p,
          'sldId'
        )

        //var targetNotesMasterIdLst = targetPresentationDoc.getElementsByTagNameNS(
        //  NAMESPACE.p,
        //  'notesMasterIdLst'
        //)[0]
        //if (!targetNotesMasterIdLst) {
        //  targetNotesMasterIdLst = targetPresentationDoc.createElement('p:notesMasterIdLst')
        //  let sldMasterIdLstEle = targetPresentationDoc.documentElement.getElementsByTagNameNS(NAMESPACE.p, 'sldMasterIdLst')[0]
        //  sldMasterIdLstEle.insertAdjacentElement('afterend', targetNotesMasterIdLst)
        //}

        targetSlideIdList.children.forEach(item => {
          maxSlideId = Math.max(maxSlideId, parseInt(item.getAttribute('id')))
        })
        maxSlideId++
        if (srcSlideNames && srcSlideNames.length) {
          srcSlideNames =
            srcSlideNames instanceof Array
              ? srcSlideNames
              : srcSlideNames.split(',')
        } else {
          // 复制全部slide
          srcSlideNames = []

          srcSlideIdList.forEach(item => {
            var slideId = item.getAttribute('r:id')
            var slideRel = srcPresentationDocRels.querySelector(`Relationship[Id=${slideId}]`)
            var slideFile = slideRel.getAttribute('Target')
            var slideFileInfo = getFileInfo(slideFile)
            srcSlideNames.push(slideFileInfo.orgName)
          })
        }

        if (insertPos === 'ajust') {
          // 根据第一个源slide的位置插入目标文档
          let insertPosEle = srcPresentationDocRels.querySelector(`Relationship[Target="slides/${srcSlideNames[0]}.xml"]`)
          insertPos = srcSlideIdList.findIndex((el, idx) => {
            return el.getAttribute('r:id') === insertPosEle.getAttribute('Id')
          }) + 1
        } else {
          insertPos = parseInt(insertPos)
        }
        if (isNaN(insertPos) || insertPos === 'afterEnd') {
          insertPos = -1
        }

        var srcSlideFiles = srcSlideNames.map(item => getSlidePath(item))
        var srcSlideObjs = await this.loadDocFiles(srcDocJSZip, srcSlideFiles)
        var fileCaches = {}

        let matchLayoutRegex = /\/slideLayout\d+\.xml/
        let matchMasterRegex = /\/slideMaster\d+\.xml/
        let matchSlidesRegex = /\/slide\d+\.xml/

        let targetContentTypesData = await this.getContentTypesData(targetDocJSZip)
        let srcContentTypesData = await this.getContentTypesData(srcDocJSZip)

        /**
         * 递归复制关联文件
         * @param {any} srcDocFile
         */
        async function copyDocFile(srcDocFile) {
          try {
            var srcDocName = srcDocFile.file.name
          } catch (ex) {
            debugger
            return
          }

          if (fileCaches[srcDocName]) {
            return fileCaches[srcDocName]
          }
          let fileInfo = srcDocFile.fileInfo
          let newFileName = `${fileInfo.orgName}_${maxSlideId}${fileInfo.ext}`
          let newFilePath = `${fileInfo.path}/${newFileName}`
          let type = srcDocFile.type || relationshipTypes.slide
          let fileData = await srcDocFile.file.async(UTF8_ARRAY)
          fileCaches[srcDocName] = newFilePath
          if (deepCopy !== false) {
            switch (type) {
              case contentTypes.slide:
              case relationshipTypes.theme:
              case relationshipTypes.chart:
              case relationshipTypes.userDefinedTags:
                if (srcDocFile.rels) {
                  var relationshipsXml = [
                    XML_FILE_PREFIX,
                    PACKAGE_RELATIONSHAIPS_XML
                  ]
                  for (var i = 0; i < srcDocFile.rels.length; i++) {
                    let relFile = srcDocFile.rels[i]
                    if (!relFile.targetFile || relFile.targetFile === 'NULL') {
                      continue
                    }
                    let needDeepCopy = true
                    let needAddContentTypes = true
                    let relTargetFileInfo = getFileInfo(relFile.targetFile)
                    let newRelFileTarget = ''
                    if (relFile.target.fileInfo.ext === '.xml') {
                      if (matchLayoutRegex.test(relFile.targetFile)) {
                        // 添加slideLayout#.xml
                        newRelFileTarget = `../slideLayouts/${defaultSlideLayout}.xml`
                        needDeepCopy = false
                        needAddContentTypes = false
                      } else if (matchMasterRegex.test(relFile.targetFile)) {
                        // 添加slideMaster#.xml
                        newRelFileTarget = `../slideMasters/${defaultSlideMaster}.xml`
                        needDeepCopy = false
                        needAddContentTypes = false
                      } else if (matchSlidesRegex.test(relFile.targetFile)) {
                        newRelFileTarget = `../slides/${curCopyingSlide.fileInfo.orgName}_${maxSlideId}.xml`
                        needDeepCopy = false
                        needAddContentTypes = false
                      }
                    }
                    if (needDeepCopy) {
                      let absPath = await copyDocFile.apply(this, [relFile.target])
                      newRelFileTarget = `/${absPath}`
                    } else if (needAddContentTypes) {
                      //更新Content_Types文档
                      this.addContentType(targetContentTypesData, parseRelsPath(newRelFileTarget, 'ppt/slides/'), srcContentTypesData.types[`/${relFile.target.fileInfo.fullPath}`])
                    }

                    relationshipsXml.push(
                      `  <Relationship Id="${relFile.id}" Type="${relFile.type}" Target="${newRelFileTarget}"/>`
                    )
                  }

                  relationshipsXml.push('</Relationships>')
                  let relationshipsContent = relationshipsXml.join('\r\n')
                  let newRelFilePath = `${fileInfo.path}/_rels/${newFileName}.rels`
                  targetDocJSZip.file(newRelFilePath, relationshipsContent, { binary: false })
                }
            }
          } else {
            // 仅仅浅复制.xml.rels文件，不修改其内容
            let relsPath = `${srcDocFile.fileInfo.path}/_rels/${srcDocFile.fileInfo.name}.rels`
            let relsFile = srcDocJSZip.files[relsPath]
            if (relsFile != null) {
              let relsFileData = await relsFile.async(UTF8_ARRAY)
              let newRelsPath = `${fileInfo.path}/_rels/${newFileName}.rels`
              targetDocJSZip.file(newRelsPath, relsFileData, { binary: true })
            }
          }
          targetDocJSZip.file(newFilePath, fileData, { binary: true })
          //更新Content_Types文档
          this.addContentType(targetContentTypesData, `/${newFilePath}`, srcContentTypesData.types[`/${fileInfo.fullPath}`])
          return newFilePath
        }

        let newSlidePaths = []
        let curCopyingSlide = null
        for (var i = 0; i < srcSlideObjs.length; i++) {
          let srcSlide = srcSlideObjs[i]
          curCopyingSlide = srcSlide
          // 复制slide?.xml
          let newSlidePath = await copyDocFile.apply(this, [srcSlide])
          newSlidePaths.push(newSlidePath)

          // 处理presentation.xml.rels
          let relationshipEle = targetPresentationDocRels.createElement('Relationship')
          let slideId = `rId${maxSlideId}`
          relationshipEle.setAttribute('Id', slideId)
          relationshipEle.setAttribute('Type', relationshipTypes.slide)
          relationshipEle.setAttribute(
            'Target',
            `slides/${srcSlide.fileInfo.orgName}_${maxSlideId}.xml`
          )
          targetSlideRels.insertAdjacentHTML('beforeend', relationshipEle.outerHTML)

          // 处理presentation.xml
          let sldIdEle = targetPresentationDoc.createElement('p:sldId')
          sldIdEle.setAttribute('id', maxSlideId)
          sldIdEle.setAttribute('r:id', slideId)
          if (
            insertPos === -1 ||
            insertPos >= targetSlideIdList.children.length
          ) {
            targetSlideIdList.appendChild(sldIdEle)
          } else {
            targetSlideIdList.insertAt(sldIdEle, insertPos)
            insertPos++
          }

          progressCB &&
            progressCB({
              percent: ((i + 1) * 100) / srcSlideObjs.length,
              slide: srcSlide
            })
          maxSlideId++
          maxSlideNO++
        }

        // 更新presentation.xml、presentation.xml.rels
        targetDocJSZip.file(
          PRESENTATION_PATH,
          targetPresentationDoc.documentElement.outerHTML,
          { binary: false }
        )
        targetDocJSZip.file(
          PRESENTATION_RELS_PATH,
          targetPresentationDocRels.documentElement.outerHTML,
          { binary: false }
        )

        // 更新[Content_Types].xml
        targetDocJSZip.file(
          CONTENT_TYPE_FILE,
          targetContentTypesData.doc.documentElement.outerHTML,
          { binary: false }
        )
        return {
          insertPos,
          slidePaths: newSlidePaths
        }
      },

      /**
       * 克隆当前文档的一个slide
       * @param {any} srcSlideName
       * @param {any} newSlideName
       */
      async cloneSlide(srcDocJSZip, srcSlideName, processCB, insertPos, defaultSlideLayout, defaultSlideMaster) {
        if (insertPos === null || insertPos === undefined) {
          insertPos = 'ajust'
        }
        let res = await this.copySlide(srcDocJSZip, srcDocJSZip, srcSlideName, processCB, insertPos, true, defaultSlideLayout, defaultSlideMaster)
        let fileInfo = getFileInfo(res.slidePaths[0])
        return {
          insertPos: res.insertPos,
          fullPath: fileInfo.fullPath,
          slideName: fileInfo.orgName
        }
      },

      /**
       * 获取指定文件的rels集合
       * @param {any} docJSZip
       * @param {any} slideName
       */
      async getFileRels(docJSZip, fileInfo) {
        let relsFilePath = `${fileInfo.path}/_rels/${fileInfo.name}.rels`
        let relsFile = docJSZip.files[relsFilePath]
        if (!relsFile) {
          throw new Error(`rels 文件[${fileInfo.fullPath}]不存在！`)
        }
        let doc = await this.getXmlDocument(relsFile)
        let relEles = doc.querySelectorAll('Relationship')
        let rels = {}
        relEles.forEach(ele => {
          let id = ele.getAttribute('Id')
          let target = ele.getAttribute('Target')
          rels[id] = {
            type: ele.getAttribute('Type'),
            target,
            targetFile: target,
            ele,
            id
          }
        })
        return {
          doc,
          rels,
          fullPath: relsFilePath
        }
      },

      /**
       * 获取指定dom对象对应的自定义参数
       * @param {any} docJSZip
       * @param {any} ele
       * @param {any} fileInfo 文档元素所属的文件信息对象  ppt/slides/slide#.xml
       * @param {any} relsObj
       * @param {any} tagSelector
       */
      async getElementTagConfig(docJSZip, ele, fileInfo, relsObj, tagSelector) {
        if (!relsObj) {
          relsObj = await this.getFileRels(docJSZip, fileInfo)
        }
        if (!relsObj) {
          return null
        }
        tagSelector = tagSelector || 'custDataLst tags'
        let tagEle = ele.querySelector(tagSelector)
        if (!tagEle) {
          return null
        }
        let tagRelId = tagEle.getAttribute('r:id')
        if (!tagRelId) {
          return null
        }
        let tagRelEle = relsObj.rels[tagRelId]
        if (!tagRelEle || tagRelEle.type !== relationshipTypes.userDefinedTags) {
          return null
        }
        let tagRelTarget = tagRelEle.target
        if (!tagRelTarget || tagRelTarget.toString().toLowerCase() === 'null') {
          return null
        }

        let tagRelTargetPath = parseRelsPath(tagRelTarget, fileInfo.path)
        let tagsRelsFile = docJSZip.files[tagRelTargetPath]
        if (!tagsRelsFile) {
          return null
        }
        let tagContentDoc = await this.getXmlDocument(tagsRelsFile)

        let tagConfigEle = tagContentDoc.querySelector('tagLst>tag[name=CONFIG]')
        if (!tagConfigEle) {
          return null
        }
        let tagConfigText = tagConfigEle.getAttribute('val')
        if (!tagConfigText) {
          return null
        }
        return {
          target: tagRelTarget,
          path: tagRelTargetPath,
          data: decodeURIComponent(escape(atob(tagConfigText))), // 解决中文乱码的问题
          doc: tagContentDoc,
          ele: tagConfigEle
        }
      },

      /**
       * 更新图片元素的图片
       * @param {any} docJSZip
       * @param {any} element
       * @param {any} dataObject
       * @param {any} fileInfo ppt/slides/slide#.xml
       * @param {any} relsObj
       * @param {any} blipSelector
       * @param {any} tagSelector
       */
      async updateImageElement(docJSZip, element, dataObject, fileInfo, relsObj, blipSelector, tagSelector) {
        var tagsData = null
        if (tagSelector != false) {
          tagSelector = tagSelector || 'nvPicPr custDataLst tags'
          tagsData = await this.getElementTagConfig(docJSZip, element, fileInfo, relsObj, tagSelector)
          if (!tagsData) {
            return false
          }
        } else {
          let cNvPr = element.querySelector('nvPicPr cNvPr')
          let eleName = cNvPr.getAttribute('name')
          tagsData = dataObject.configs[eleName]
          if (!tagsData) {
            return false
          }
        }

        let tagsConfig = tagsData.Config
        if (!tagsData.Config) {
          let tagsConfigText = tagsData.data
          if (!tagsConfigText) {
            return false
          }
          tagsConfig = JSON.parse(tagsConfigText)
        }

        let imgFileData = null
        try {
          if (tagsConfig.src) {
            //替换图片文件
            imgFileData = Object.getValue(dataObject, tagsConfig.src)
            if (!imgFileData) {
              return false
            }
          } else if (tagsConfig.handler) {
            let handler = new Function('dataObject', tagsConfig.handler)
            imgFileData = await handler.apply(this, [dataObject])
          }
        } catch (ex) {
          debugger
          console.error(ex.message)
        }
        if (imgFileData == null) {
          return false;
        }
        blipSelector = blipSelector || 'blipFill > blip'
        let blip = element.querySelector(blipSelector)
        if (!blip) {
          return false
        }
        let rid = blip.getAttribute('r:embed')
        let relsFileDoc = relsObj.doc
        let relsPicEle = relsObj.rels[rid]
        let relsPicTarget = relsPicEle.target
        if (!relsPicTarget || relsPicTarget.toString().toLowerCase() === 'null') {
          relsPicTarget = '../media/null.png'
        }
        let relsPicTargetPath = parseRelsPath(relsPicTarget, fileInfo.path)

        let relsPicTargetRelativeFileInfo = this.getFileInfo(relsPicTarget)
        let relsPicTargetPathFileInfo = this.getFileInfo(relsPicTargetPath)
        let newImgFileTargetPath = `${relsPicTargetRelativeFileInfo.path}/${imgFileData.name}`
        let relsRootPath = ''
        switch (fileInfo.path) {
          case 'word':
            relsRootPath = 'word'
            break
        }
        let newImgFilePath = joinPaths(relsRootPath, relsPicTargetPathFileInfo.path, imgFileData.name)
        // relsPicTargetPath !== this.NullImage.name && docJSZip.remove(relsPicTargetPath) // 删除原来的图片文件
        docJSZip.file(newImgFilePath, imgFileData.data, { binary: true })
        //修改xml文档节点的内容，更新引用图片的名称
        relsPicEle.ele.setAttribute('Target', newImgFileTargetPath)
        return true
      },

      /**
       * 更新文本元素的文本内容
       * @param {any} docJSZip
       * @param {any} element
       * @param {any} dataObject
       * @param {any} fileInfo ppt/slides/slide#.xml
       * @param {any} relsObj
       * @param {any} textSelector
       * @param {any} tagSelector
       */
      async updateTextElement(docJSZip, element, dataObject, fileInfo, relsObj, textSelector, tagSelector) {
        tagSelector = tagSelector || 'nvSpPr nvPr custDataLst tags'
        let tagsData = await this.getElementTagConfig(docJSZip, element, fileInfo, relsObj, tagSelector)
        if (!tagsData) {
          return false
        }
        textSelector = textSelector || 'txBody > p > r > t'
        let contentEle = element.querySelector(textSelector)
        if (!contentEle) {
          return false
        }
        let tagsConfigText = tagsData.data
        if (!tagsConfigText) {
          return false
        }
        try {
          let tagsConfig = JSON.parse(tagsConfigText)
          let content = ''

          if (tagsConfig.tpl) {
            content = template.render(tagsConfig.tpl, dataObject)
          } else if (tagsConfig.handler) {
            let handler = new Function('dataObject', 'template', tagsConfig.handler)
            content = await handler.apply(this, [dataObject, template])
          }
          contentEle.innerHTML = content
        } catch (ex) {
          debugger
          console.error(ex.message)
        }
        element.querySelectorAll('txBody > p > r + r').forEach(ele => {
          ele.remove()
        })
        return true
      },

      /**
       * 处理Word中的文本框
       * @param {any} docJSZip
       * @param {any} element
       * @param {any} dataObject
       * @param {any} fileInfo
       * @param {any} relsObj
       */
      async updateWordTextElement(docJSZip, element, dataObject, fileInfo, relsObj) {
        let docPr = element.querySelector('Choice drawing docPr')
        if (!docPr) {
          return false
        }
        let eleName = docPr.getAttribute('name')
        let tagsData = dataObject.configs[eleName]
        if (!tagsData) {
          return false
        }

        if (!tagsData.Config || !tagsData.Config.replace) {
          return false
        }

        let pEle = element.querySelector('Choice drawing graphic wsp txbx txbxContent p')
        if (!pEle) {
          return false
        }


        let txtEle = pEle.querySelector('r t')
        if (!txtEle) {
          return false
        }
        let pEles = []
        let content = txtEle.textContent
        content.split('\n').forEach(txt => {
          let ele = pEle.cloneNode(true)
          let tEle = ele.querySelector('r t')
          tEle.textContent = txt
          pEles.push(ele)
        })

        let nextSiblingEle = element.parentNode
        while (nextSiblingEle && nextSiblingEle.localName.toLowerCase() != 'p') {
          nextSiblingEle = nextSiblingEle.parentNode
        }
        if (!nextSiblingEle) {
          return false
        }
        pEles.reverse().forEach(ele => {
          nextSiblingEle.insertAdjacentElement('afterend', ele)
        })
        element.remove()
        return true
      },

      /**
       * 更新图表元素的数据，包括展示数据和Excel数据
       * @param {any} docJSZip
       * @param {any} element
       * @param {any} dataObject
       * @param {any} fileInfo ppt/slides/slide#.xml
       * @param {any} relsObj
       * @param {any} chartSelector
       * @param {any} tagSelector
       */
      async updateChartElement(docJSZip, element, dataObject, fileInfo, relsObj, tagSelector) {
        var tagsData = null
        if (tagSelector != false) {
          tagSelector = tagSelector || 'nvGraphicFramePr > nvPr > custDataLst > tags'
          tagsData = await this.getElementTagConfig(docJSZip, element.parentNode.parentNode.parentNode, fileInfo, relsObj, tagSelector)
          if (!tagsData) {
            return false
          }
        } else {
          let docPr = element.parentNode.parentNode.parentNode.querySelector('docPr,cNvPr')
          let eleName = docPr.getAttribute('name')
          tagsData = dataObject.configs[eleName]
          if (!tagsData) {
            return false
          }
        }

        let tagsConfig = tagsData.Config
        if (!tagsData.Config) {
          let tagsConfigText = tagsData.data
          if (!tagsConfigText) {
            return false
          }
          tagsConfig = JSON.parse(tagsConfigText)
        }

        let datas = null
        try {
          if (tagsConfig.dataSource) {
            datas = Object.getValue(dataObject, tagsConfig.dataSource) // 要绑定的数据源
          } else if (tagsConfig.handler) {
            let handler = new Function('dataObject', tagsConfig.handler)
            datas = await handler.apply(this, [dataObject])
          }
        } catch (ex) {
          debugger
          console.error(ex.message)
        }

        datas = datas || []
        let seriesConfigs = tagsConfig.series // 配置Chart的Series参数
        let tableCols = tagsConfig.columns    // 配置Excel数据源的表格列头
        if (!seriesConfigs || !tableCols) {
          return false
        }

        let chartRId = element.getAttribute('r:id')
        if (!chartRId) {
          return false
        }
        let relsEle = relsObj.rels[chartRId]
        if (!relsEle) {
          return false
        }
        if (relsEle.type !== relationshipTypes.chart) {
          return false
        }
        let chartTarget = relsEle.target
        if (!chartTarget) {
          return false
        }
        let relsRootPath = ''
        switch (fileInfo.path) {
          case 'word':
            relsRootPath = 'word'
            break
        }
        let chartTargetFilePath = parseRelsPath(chartTarget, fileInfo.path)
        chartTargetFilePath = joinPaths(relsRootPath, chartTargetFilePath)
        let chartTargetFile = docJSZip.files[chartTargetFilePath]
        if (!chartTargetFile) {
          console.error(`chart target file '${chartTargetFilePath}' not found!`)
          return false
        }
        let chartTargetFileInfo = getFileInfo(chartTargetFilePath)

        let chartDoc = await this.getXmlDocument(chartTargetFile)
        if (!chartDoc) {
          console.error(`chart documnt file '${chartTargetFile}' not found!`)
          return false
        }

        function generatorExcelRefVal(refVal, length) {
          let vals = refVal.split(':')
          let startIdx = parseInt(vals[0].match(/\d+$/))
          let lastIdx = startIdx + length - 1
          return refVal.replace(/\d+$/, lastIdx)
        }

        let serSelector = 'chart > plotArea ser'
        let seriesElements = chartDoc.querySelectorAll(serSelector)
        if (!seriesElements || !seriesElements.length) {
          console.error(`chart's xml structure error!`)
          return false
        }
        //更新XML数据
        await seriesElements.forEach(async seriesEle => {
          let idxEle = seriesEle.querySelector('idx')
          if (!idxEle) {
            return
          }
          let seriesIdx = parseInt(idxEle.getAttribute('val'))
          if (isNaN(seriesIdx) || seriesIdx < 0) {
            return
          }
          let seriesConfig = seriesConfigs[seriesIdx]
          if (!seriesConfig) {
            return
          }

          let chartType = tagsConfig.chartType || 'default'

          let chartXmlRenders = {
            default: async () => {
              //一般chart，默认的Xml内容渲染器
              let seriesDataTitleField = seriesConfig.titleField || 'title'
              let seriesDataValueField = seriesConfig.valueField || 'value'
              let defVal = seriesConfig.defVal
              if (defVal === undefined) {
                defVal = null
              }
              // 更新strRef
              let strRefSelector = 'cat > strRef'
              let strRefEle = seriesEle.querySelector(strRefSelector)
              if (!strRefEle) {
                return
              }

              //更新strRef >> 更新Excel数据引用范围定义
              let strRefValEle = strRefEle.querySelector('f')
              if (strRefValEle) {
                strRefValEle.innerHTML = generatorExcelRefVal(strRefValEle.innerHTML, datas.length)
              }

              //更新strRef >> 更新strCache节点内容
              let strCacheEle = strRefEle.querySelector('strCache')
              let strCache_ptCountEle = strCacheEle.querySelector('ptCount')
              strCache_ptCountEle.setAttribute('val', datas.length)
              let ptEles = strCacheEle.querySelectorAll('pt')
              if (ptEles && ptEles.length) {
                await ptEles.forEach(ptEle => {
                  ptEle.remove()
                })
              } else {
                debugger
              }

              let numCacheSelector = 'val > numRef'
              let numRefEle = seriesEle.querySelector(numCacheSelector)
              let numRefValEle = numRefEle.querySelector('f')
              if (numRefValEle) {
                numRefValEle.innerHTML = generatorExcelRefVal(numRefValEle.innerHTML, datas.length)
              }
              let numCacheEle = numRefEle.querySelector('numCache')
              let numCache_ptCountEle = numCacheEle.querySelector('ptCount')
              numCache_ptCountEle.setAttribute('val', datas.length)

              ptEles = numCacheEle.querySelectorAll('pt')
              if (ptEles && ptEles.length) {
                await ptEles.forEach(ptEle => {
                  ptEle.remove()
                })
              } else {
                debugger
              }
              let titleHtmls = []
              let valHtmls = []
              datas.forEach((data, idx) => {
                if (seriesConfig.handler) {
                  if (seriesConfig.handler(seriesEle, data, idx) == false) {
                    return
                  }
                }
                titleHtmls.push(`<c:pt idx="${idx}"><c:v>${data[seriesDataTitleField]}</c:v></c:pt>`)
                let val = data[seriesDataValueField]
                if (val === null) {
                  val = defVal
                }
                if (val !== null) {
                  valHtmls.push(`<c:pt idx="${idx}"><c:v>${val}</c:v></c:pt>`)
                }
              })
              strCacheEle.insertAdjacentHTML('beforeend', titleHtmls.join('\n'))
              numCacheEle.insertAdjacentHTML('beforeend', valHtmls.join('\n'))
            },
            scatter: async () => {
              //散点图(scatterChart)的Xml内容渲染器
              let seriesDataXValueField = seriesConfig.xValueField || 'xValue'
              let seriesDataYValueField = seriesConfig.yValueField || 'yValue'
              let seriesPointStyleField = seriesConfig.styleField || 'style'
              let defXValue = seriesConfig.defXValue
              let defYValue = seriesConfig.defYValue
              if (defXValue === undefined) {
                defXValue = null
              }
              if (defYValue === undefined) {
                defYValue = null
              }
              //更新轴的XML数据
              let updateAxisXmlData = async (axisName, axisValuesHtml) => {
                let refSelector = `${axisName}Val > numRef`
                let numRefEle = seriesEle.querySelector(refSelector)
                if (!numRefEle) {
                  return false
                }

                //更新轴的Excel数据引用范围定义
                let numRefValEle = numRefEle.querySelector('f')
                if (numRefValEle) {
                  numRefValEle.innerHTML = generatorExcelRefVal(numRefValEle.innerHTML, datas.length)
                }

                //更新轴的numCache节点内容
                let numCacheEle = numRefEle.querySelector('numCache')
                let valCache_ptCountEle = numCacheEle.querySelector('ptCount')
                valCache_ptCountEle.setAttribute('val', datas.length)
                let ptEles = numCacheEle.querySelectorAll('pt')
                if (ptEles && ptEles.length) {
                  await ptEles.forEach(ptEle => {
                    ptEle.remove()
                  })
                } else {
                  debugger
                }
                numCacheEle.insertAdjacentHTML('beforeend', axisValuesHtml)
              }

              let updatePointStyleXmlData = async (pointStyleXml) => {
                let dPtSelector = 'dPt'
                let dPtEles = seriesEle.querySelectorAll(dPtSelector)
                await dPtEles.forEach(ele => {
                  ele.remove()
                })
                seriesEle.insertAdjacentHTML('beforeend', pointStyleXml)
              }

              let xValues = []
              let yValues = []
              let pointStyles = []
              datas.forEach((data, idx) => {
                if (seriesConfig.valueHandler) {
                  if (seriesConfig.valueHandler({ seriesEle, data, index }) == false) {
                    return
                  }
                }
                //生成X轴的XML数据
                let xVal = data[seriesDataXValueField]
                if (xVal === null) {
                  xVal = defXValue
                }
                xValues.push(`<c:pt idx="${idx}"><c:v>${xVal}</c:v></c:pt>`)

                //生成Y轴的XML数据
                let yVal = data[seriesDataYValueField]
                if (yVal === null) {
                  yVal = defYValue
                }
                yValues.push(`<c:pt idx="${idx}"><c:v>${yVal}</c:v></c:pt>`)

                let style = data[seriesPointStyleField]
                if (style) {
                  let color = style.color || '0000FF'
                  let size = style.size || 5
                  let symbol = style.symbol || 'circle'
                  pointStyles.push(`<c:dPt><c:idx val="${idx}"/><c:marker><c:symbol val="${symbol}"/><c:size val="${size}"/><c:spPr><a:solidFill><a:srgbClr val="${color}"/></a:solidFill><a:ln w="9525"><a:noFill/></a:ln><a:effectLst/></c:spPr></c:marker><c:bubble3D val="0"/></c:dPt>`)
                }
              })
              await updateAxisXmlData('x', xValues.join('\n'))
              await updateAxisXmlData('y', yValues.join('\n'))
              await updatePointStyleXmlData(pointStyles.join('\n'))
              seriesConfig.updateHandler && seriesConfig.updateHandler({ seriesEle, datas, tagsConfig, seriesIdx })
            },
            line: async () => {
              //一般chart，默认的Xml内容渲染器
              let seriesDataXValueField = seriesConfig.xValueField || 'xValue'
              let seriesDataYValueField = seriesConfig.yValueField || 'yValue'
              let defVal = seriesConfig.defVal
              if (defVal === undefined) {
                defVal = null
              }
              debugger
              // 更新strRef
              let strRefSelector = 'cat>strRef,cat>numRef'
              let strRefEle = seriesEle.querySelector(strRefSelector)
              if (!strRefEle) {
                return
              }

              //更新strRef >> 更新Excel数据引用范围定义
              let strRefValEle = strRefEle.querySelector('f')
              if (strRefValEle) {
                strRefValEle.innerHTML = generatorExcelRefVal(strRefValEle.innerHTML, datas.length)
              }

              //更新strRef >> 更新strCache节点内容
              let strCacheEle = strRefEle.querySelector('strCache,numCache')
              let strCache_ptCountEle = strCacheEle.querySelector('ptCount')
              strCache_ptCountEle.setAttribute('val', datas.length)
              let ptEles = strCacheEle.querySelectorAll('pt')
              if (ptEles && ptEles.length) {
                await ptEles.forEach(ptEle => {
                  ptEle.remove()
                })
              } else {
                debugger
              }

              let numCacheSelector = 'val>numRef'
              let numRefEle = seriesEle.querySelector(numCacheSelector)
              let numRefValEle = numRefEle.querySelector('f')
              if (numRefValEle) {
                numRefValEle.innerHTML = generatorExcelRefVal(numRefValEle.innerHTML, datas.length)
              }
              let numCacheEle = numRefEle.querySelector('numCache')
              let numCache_ptCountEle = numCacheEle.querySelector('ptCount')
              numCache_ptCountEle.setAttribute('val', datas.length)
              ptEles = numCacheEle.querySelectorAll('pt')
              if (ptEles && ptEles.length) {
                await ptEles.forEach(ptEle => {
                  ptEle.remove()
                })
              } else {
                debugger
              }
              let titleHtmls = []
              let valHtmls = []
              datas.forEach((data, idx) => {
                if (seriesConfig.handler) {
                  if (seriesConfig.handler(seriesEle, data, idx) == false) {
                    return
                  }
                }
                titleHtmls.push(`<c:pt idx="${idx}"><c:v>${data[seriesDataXValueField]}</c:v></c:pt>`)
                let val = data[seriesDataYValueField]
                if (val === null) {
                  val = defVal
                }
                if (val !== null) {
                  valHtmls.push(`<c:pt idx="${idx}"><c:v>${val}</c:v></c:pt>`)
                }
              })
              strCacheEle.insertAdjacentHTML('beforeend', titleHtmls.join('\n'))
              numCacheEle.insertAdjacentHTML('beforeend', valHtmls.join('\n'))
            }
          }

          let chartRender = chartXmlRenders[chartType]
          if (chartRender) {
            await chartRender()
          }
        })

        //更新Excel数据源文件
        let chartRelsObj = await this.getFileRels(docJSZip, chartTargetFileInfo)
        let externalDataEle = chartDoc.querySelector('externalData')
        let externalDataRId = externalDataEle.getAttribute('r:id')
        let chartRelsEle = chartRelsObj.rels[externalDataRId]
        let excelTarget = chartRelsEle.target
        if (!excelTarget) {
          return false
        }
        let excelTargetPath = parseRelsPath(excelTarget, fileInfo.path)
        excelTargetPath = joinPaths(relsRootPath, excelTargetPath)
        let excelTargetFile = docJSZip.files[excelTargetPath]
        let excelTargetFileData = await excelTargetFile.async(UTF8_ARRAY)
        let excelJSZip = new JSZip()
        await excelJSZip.loadAsync(excelTargetFileData)
        let sheet1FilePath = `xl/worksheets/sheet1.xml`
        let sheet1File = excelJSZip.files[sheet1FilePath]
        let sheet1Doc = await this.getXmlDocument(sheet1File)
        let dimensionEle = sheet1Doc.querySelector('dimension')
        dimensionEle.setAttribute('ref', `A1:${String.fromCharCode(65 + tableCols.length - 1)}${datas.length + 1}`)
        let sheetDataEle = sheet1Doc.querySelector('sheetData')

        //删除原来的数据行
        await sheetDataEle.querySelectorAll('row + row').forEach(ele => {
          ele.remove()
        })

        //获取sharedStrings.xml文档对象
        let sharedStringsFilePath = 'xl/sharedStrings.xml'
        let sharedStringsFile = excelJSZip.files[sharedStringsFilePath]
        let sharedStringsDoc = await this.getXmlDocument(sharedStringsFile)
        let sstEle = sharedStringsDoc.documentElement

        //新增数据行
        let rows = []
        datas.forEach((data, rowIdx) => {
          let cells = tableCols.map((col, cellIdx) => {
            let cellName = `${String.fromCharCode(65 + cellIdx)}${rowIdx + 2}`
            let val = data[col]
            let cellType = ''
            if (cellIdx === 0) {
              //处理列头引用sharedStrings
              sstEle.insertAdjacentHTML('beforeend', `<si><t>${val}</t></si>`)
              val = sstEle.children.length - 1
              cellType = 't="s"'
            }
            if (val !== null) {
              return `<c r="${cellName}" ${cellType}><v>${val}</v></c>`
            } else {
              return `<c r="${cellName}"/>`
            }
          })
          rows.push(`<row r="${rowIdx + 2}">${cells.join('')}</row>`)
        })
        sheetDataEle.insertAdjacentHTML('beforeEnd', rows.join('\n'))

        //更新sharedStrings.xml
        sstEle.setAttribute('count', sstEle.children.length)
        sstEle.setAttribute('uniqueCount', sstEle.children.length)
        excelJSZip.file(sharedStringsFilePath, sharedStringsDoc.documentElement.outerHTML)

        //更新Excel文档
        excelJSZip.file(sheet1FilePath, sheet1Doc.documentElement.outerHTML)
        excelTargetFileData = await excelJSZip.generateAsync({ type: UTF8_ARRAY })
        //更新xlsx文件
        docJSZip.file(excelTargetPath, excelTargetFileData)

        if (tagsConfig.title) {
          //更新chart对象的title
          let titleEle = chartDoc.documentElement.querySelector('chart > title')
          if (titleEle) {
            let titleREles = titleEle.querySelectorAll('r')
            if (titleREles && titleREles.length > 1) {
              titleREles.forEach((ele, idx) => {
                idx > 0 && ele.remove()
              })
            }
            titleREle = titleREles[0]
            if (titleREle) {
              let titleTextEle = titleREle.querySelector('t')
              if (titleTextEle) {
                titleTextEle.innerHTML = template.render(tagsConfig.title, dataObject)
              }
            }
          } else {
            console.error('更新chart的title失败，未找到title节点。')
          }
        }

        //更新chart#.xml文件
        docJSZip.file(chartTargetFilePath, chartDoc.documentElement.outerHTML)
        return true
      },

      /**
       * 更新表格对象的数据
       * @param {any} docJSZip
       * @param {any} element
       * @param {any} dataObject
       * @param {any} fileInfo
       * @param {any} relsObj
       * @param {any} tblSelector
       * @param {any} tagSelector
       */
      async updateTableElement(docJSZip, element, dataObject, fileInfo, relsObj, tblSelector, tagSelector) {
        tagSelector = tagSelector || 'nvGraphicFramePr > nvPr > custDataLst > tags'
        let tagsData = await this.getElementTagConfig(docJSZip, element.parentNode.parentNode.parentNode, fileInfo, relsObj, tagSelector)
        if (!tagsData) {
          return false
        }
        let tagsConfigText = tagsData.data
        if (!tagsConfigText) {
          return false
        }
        let tagsConfig = JSON.parse(tagsConfigText)

        if (!tagsConfig) {
          return false
        }
        let datas = null
        try {
          if (tagsConfig.render) {
            let tblRender = new Function('element', 'dataObject', tagsConfig.render)
            await tblRender.apply(this, [element, dataObject])
            return true
          }
          if (tagsConfig.dataSource) {
            datas = Object.getValue(dataObject, tagsConfig.dataSource) // 要绑定的数据源
          } else if (tagsConfig.handler) {
            let handler = new Function('dataObject', tagsConfig.handler)
            datas = await handler.apply(this, [dataObject])
          }
        } catch (ex) {
          debugger
          console.error(ex.message)
        }

        datas = datas || []
        let tableCols = tagsConfig.columns    // 表格列头
        if (tableCols && tableCols.IsFunction) {
          tableCols = tableCols.apply(this, [dataObject])
        }
        let firstRow = (tagsConfig.firstRow === null || tagsConfig === undefined) ? 1 : tagsConfig.firstRow
        firstRow = firstRow >= 0 ? firstRow : 1
        let rows = element.querySelectorAll('tr')
        let rowTmp = rows[firstRow].cloneNode(true)
        await rows.forEach((row, idx) => {
          if (idx > 0 || firstRow === 0) {
            row.remove()
          }
        })
        let maxRows = tagsConfig.maxRows || 0
        if (maxRows > 0) {
          datas = datas.slice(0, maxRows)
        }
        datas && datas.forEach(data => {
          let row = rowTmp.cloneNode(true)
          let cells = row.querySelectorAll('tc')
          tableCols.forEach((col, cellIdx) => {
            let cell = cells[cellIdx]
            cell.querySelectorAll('txBody > p > r + r').forEach(r => {
              r.remove()
            })
            let txtEle = cell.querySelector('txBody > p > r > t')
            let val = data[col]
            if (val === undefined || val === null) {
              val = '--'
            }
            txtEle.innerHTML = val
          })
          element.insertAdjacentHTML('beforeend', row.outerHTML)
        })
        return true
      },

      /**
       * 
       * @param {any} tblElement
       * @param {any} datas
       */
      updateTableElementColumns(tblElement, datas, tplCellIndex) {
        tplCellIndex = tplCellIndex || 0
        let rows = tblElement.querySelectorAll('tr')
        let tblGridEle = tblElement.querySelector('tblGrid')
        let tplCol = tblGridEle.querySelectorAll('gridCol')[tplCellIndex]
        datas.forEach(data => {
          tblGridEle.insertAdjacentHTML('beforeend', tplCol.outerHTML)
        })
        rows.forEach((rowEle, rowIndex) => {
          let cells = rowEle.querySelectorAll('tc')
          let tplCell = cells[tplCellIndex].cloneNode(true)
          tplCell.querySelectorAll('txBody > p > r + r').forEach(r => {
            r.remove()
          })
          let cellHtmls = []
          datas.forEach(data => {
            let newCell = tplCell.cloneNode(true)
            let txtEle = newCell.querySelector('txBody > p > r > t')
            let val = data[rowIndex]
            if (val === undefined || val === null) {
              val = '--'
            }
            txtEle.innerHTML = val
            cellHtmls.push(newCell.outerHTML)
          })
          rowEle.insertAdjacentHTML('beforeend', cellHtmls.join(''))
        })
        tblElement.innerHTML = tblElement.innerHTML.replace(new RegExp('<a:tc xmlns:a="http://schemas\\.openxmlformats\\.org/drawingml/2006/main"', 'gi'), '<a:tc')
      },

      /**
       * 删除指定名称的slide，多个slide用，号间隔
       * @param {any} docJSZip
       * @param {any} slideNames
       */
      async removeSlides(docJSZip, slideNames) {
        if (!slideNames) {
          return
        }
        if (slideNames.IsString) {
          slideNames = slideNames.split(',')
        }
        let presentationConfig = await this.getPresentationConfig(docJSZip)
        let sldIdEles = []
        sldIdEles.push.apply(sldIdEles, presentationConfig.presentationDoc.querySelectorAll(`sldIdLst>sldId`))
        let contentTypeFile = docJSZip.files[CONTENT_TYPE_FILE]
        let contentTypeDoc = await this.getXmlDocument(contentTypeFile)
        let overrideEles = Array.prototype.concat.apply([], contentTypeDoc.querySelectorAll('Override'))
        await slideNames.forEachAsync(async sliedName => {
          let relsEle = presentationConfig.presentationDocRels.querySelector(`Relationship[Target="slides/${sliedName}.xml"]`)
          if (!relsEle) {
            return
          } `1`
          let rid = relsEle.getAttribute('Id')
          let sldIdEle = sldIdEles.find(item => {
            return item.getAttribute('r:id') === rid
          })
          if (sldIdEle) {
            sldIdEle.remove()
          }
          relsEle.remove()
          let slidePath = `ppt/slides/${sliedName}.xml`
          docJSZip.remove(slidePath)
          docJSZip.remove(`ppt/slides/_rels/${sliedName}.xml.rels`)

          //删除Content_Types中的定义
          let contentTypeEle = overrideEles.find(ele => ele.getAttribute('PartName') === `/${slidePath}`)
          if (contentTypeEle) {
            contentTypeEle.remove()
          }
        })
        docJSZip.file(PRESENTATION_PATH, presentationConfig.presentationDoc.documentElement.outerHTML)
        docJSZip.file(PRESENTATION_RELS_PATH, presentationConfig.presentationDocRels.documentElement.outerHTML)
        docJSZip.file(CONTENT_TYPE_FILE, contentTypeDoc.documentElement.outerHTML)
      },
    }
    return openxmlXPInstance
  }

  // module export
  if (typeof define === typeof function () { } && define.amd) { // AMD
    define('openxmlXP', ['JSZip', 'template'], function (jsZip, template) {
      return defineOpenXmlXP(jsZip, template)
    })
  } else if (typeof module !== typeof undefined && module.exports) { // Node
    module.exports = defineOpenXmlXP(JSZip, template)
  } else {
    root.openxmlXP = defineOpenXmlXP(JSZip, template)
  }
})(this)
